#include "SegmentInventory.h"
#include "DB.h"

//#define DEBUG_SegInv

SegmentInventory::SegmentInventory(unsigned pageCount, BufferManager& buffMan) : pageCount(pageCount), freeCount(pageCount - 1)
{
#ifdef DEBUG_SegInv
    cout << "Constructor SegmentInventory" << endl;
#endif
    SEM_CREATE(fileSemaphore);
    createInventory(buffMan);
}

vector<unsigned> SegmentInventory::requestPages(unsigned segmentId, unsigned min, unsigned max)
{
#ifdef DEBUG_SegInv
    cout << "requestPages" << endl;
#endif
    SEM_WRITELOCK(fileSemaphore);

    loadInventory();

    while (freeCount < min)
    {
        unsigned delta = grow();
        freeCount += delta;

        list<unsigned> newPages;
        for (unsigned i = 0; i < delta; i++)
        {
            newPages.push_back(pageCount + i);
        }
        merge(this->entries[0], newPages);
        pageCount += delta;
    }

    //now there should be enough free space

    int pos = -1;
    for (unsigned i = 0; i < this->segments.size(); i++)
    {
        if ((this->segments[i])->segmentId == segmentId)
        {
            pos = i;
            break;
        }
    }

    //segment noch nicht vorhanden - muss angelegt werden
    if (pos == -1)
    {
        pos = this->segments.size();
        SegmentInventory::Segment* seg = new SegmentInventory::Segment();
        seg->segmentId = segmentId;
        seg->entryCount = 0;
        this->segments.push_back(seg);
        this->entries.push_back(vector<SegmentInventory::Entry*>());

    }

    vector<unsigned> pages;
    unsigned currentSize = 0;

    for (unsigned i = 0; i < this->entries[0].size(); i++)
    {
        SegmentInventory::Entry& e = *this->entries[0][i];
        if ((e.to - e.from + currentSize) >= min)
        {
            //platz reicht aus
            if ((e.to - e.from + currentSize) > max)
            {
                //sogar zuviel platz
                while (currentSize < max)
                {
                    pages.push_back(e.from);
                    freeCount--;
                    currentSize++;
                    e.from++;
                }
            }
            else
            {
                for (unsigned n = e.from; n < e.to; n++)
                {
                    pages.push_back(n);
                    freeCount--;
                }
                delete this->entries[0][i];
                this->entries[0][i] = NULL;
                this->entries[0].erase(this->entries[0].begin() + i);
                i--;
            }
            break;
        }
        else
        {
            //platz reicht noch nicht aus
            while (e.from < e.to)
            {
                pages.push_back(e.from);
                freeCount--;
                e.from++;
            }
            delete this->entries[0][i];
            this->entries[0][i] = NULL;
            this->entries[0].erase(this->entries[0].begin() + i);
            i--;
        }
    }

    //merge
    list<unsigned> pages_list;
    for (unsigned i = 0; i < pages.size(); i++)
    {
        pages_list.push_back(pages[i]);
    }

    merge(this->entries[pos], pages_list);

    //save
    saveInventory();

    SEM_UNLOCK(fileSemaphore);

    this->clear();

    return pages;
}

unsigned SegmentInventory::getFreeCount()
{
    return this->freeCount;
}


unsigned SegmentInventory::grow()
{
#ifdef DEBUG_SegInv
    cout << "growingFile" << endl;
#endif
    unsigned oldSize = getFileSize();
    unsigned ignoreMe = truncate(DB_FILE.c_str(), pageSize * ((oldSize * DB_FILE_GROW_FACTOR) + DB_FILE_GROW_CONST));
    ignoreMe++;
    return getFileSize() - oldSize;
}

void SegmentInventory::returnPages(unsigned segId, vector<unsigned> pages)
{
#ifdef DEBUG_SegInv
    cout << "returnPages" << endl;
#endif
    SEM_WRITELOCK(fileSemaphore);
    //suche eintrag
    int segPosition = -1;
    for (unsigned i = 0; i < this->segments.size(); i++)
    {
        if (this->segments[i]->segmentId == segId)
        {
            segPosition = i;
            break;
        }
    }

    if (segPosition == -1)
    {
        return; //Segment nicht vorhanden - nicht gut
    }

    freeCount += pages.size();

    //bearbeite einträge
    list<unsigned> pageList;
    for (unsigned i = 0; i < pages.size(); i++)
    {
        //Seiten rausnehmen
        pageList.push_back(pages[i]);
    }
    list<unsigned> removeMe = pageList;
    removeIfContained(this->entries[segPosition], removeMe);

    if (this->entries[segPosition].size() == 0)
    {
        for(unsigned i = 0; i < this->entries[segPosition].size(); i++)
        {
            delete this->entries[segPosition][i];
            this->entries[segPosition][i] = NULL;
        }
        this->entries.erase(this->entries.begin() + segPosition);
        delete this->segments[segPosition];
        this->segments[segPosition] = NULL;
        this->segments.erase(this->segments.begin() + segPosition);
    }

    //return pages to free segment
    merge(this->entries[0], pageList);

    saveInventory();
    SEM_UNLOCK(fileSemaphore);
}

void SegmentInventory::loadInventory()
{
#ifdef DEBUG_SegInv
    cout << "loadInventory" << endl;
#endif
    this->clear();

    BufferFrame& frame = DB::getDB().getBufferManager().getPage(0, false); //should be true
    char* data = (char*)frame.getData();
    char* current = data + sizeof(SegmentInventory::Segment) + sizeof(SegmentInventory::Entry); //skip SegmentInventory-Entry

    unsigned currentId = 0;
    bool first = true;

    while (true)
    {
        SegmentInventory::Segment* seg = new SegmentInventory::Segment(*(SegmentInventory::Segment*)current);
        current += sizeof(SegmentInventory::Segment);
        if (!first && seg->entryCount == 0)
        {
            break;
        }
        first = false;
        this->segments.push_back(seg);
        this->entries.push_back(vector<SegmentInventory::Entry*>());
        for (unsigned i = 0; i < seg->entryCount; i++)
        {
            SegmentInventory::Entry* entry = new SegmentInventory::Entry(*(SegmentInventory::Entry*)current);
            this->entries[currentId].push_back(entry);
            current += sizeof(SegmentInventory::Entry);
        }

        currentId++;
    }

    DB::getDB().getBufferManager().unfixPage(frame, true);
}

void SegmentInventory::saveInventory()
{
#ifdef DEBUG_SegInv
    cout << "saveInventory" << endl;
#endif
    char data[pageSize] = {0};

    SegmentInventory::Segment segInv;
    segInv.entryCount = 1;
    segInv.segmentId = 0;
    SegmentInventory::Entry invEntry;
    invEntry.from = 0;
    invEntry.to = 1;

    char* current = data;
    memcpy(current, &segInv, sizeof(SegmentInventory::Segment));
    current += sizeof(SegmentInventory::Segment);
    memcpy(current, &invEntry, sizeof(SegmentInventory::Entry));
    current += sizeof(SegmentInventory::Entry);

    for (unsigned i = 0; i < this->segments.size(); i++)
    {
        (this->segments[i])->entryCount = this->entries[i].size();
        //cout << "entryCount " << (this->segments[i])->entryCount << endl;
        memcpy(current, this->segments[i], sizeof(SegmentInventory::Segment));
        current += sizeof(SegmentInventory::Segment);
        for (unsigned n = 0; n < this->entries[i].size(); n++)
        {
            memcpy(current, this->entries[i][n], sizeof(SegmentInventory::Entry));
            current += sizeof(SegmentInventory::Entry);
        }
    }

    SegmentInventory::Segment dummy;
    dummy.entryCount = 0;
    dummy.segmentId = 0;
    memcpy(current, &dummy, sizeof(SegmentInventory::Segment));

    BufferFrame& frame = DB::getDB().getBufferManager().getPage(0, false);
    char* oldData = (char*)frame.getData();
    memcpy(oldData, data, pageSize);

    DB::getDB().getBufferManager().unfixPage(frame, true);
}

void SegmentInventory::createInventory(BufferManager& buffMan)
{
#ifdef DEBUG_SegInv
    cout << "createInventory" << endl;
#endif
    SegmentInventory::Segment segInv;;
    segInv.entryCount = 1;
    segInv.segmentId = 0;
    SegmentInventory::Entry invEntry;
    invEntry.from = 0;
    invEntry.to = 1;

    SegmentInventory::Segment freeSeg;
    freeSeg.entryCount = 1;
    freeSeg.segmentId = 1;
    SegmentInventory::Entry freeEntry;
    freeEntry.from = 1;
    freeEntry.to = this->pageCount;

    this->segments.push_back(new SegmentInventory::Segment(freeSeg));
    this->entries.push_back(vector<SegmentInventory::Entry*>());
    this->entries[0].push_back(new SegmentInventory::Entry(freeEntry));

    BufferFrame& frame = buffMan.getPage(0, false); //should be true
    char* data = (char*)frame.getData();

    char* current = data;
    memcpy(current, &segInv, sizeof(SegmentInventory::Segment));
    current += sizeof(SegmentInventory::Segment);
    memcpy(current, &invEntry, sizeof(SegmentInventory::Entry));
    current += sizeof(SegmentInventory::Entry);
    memcpy(current, &freeSeg, sizeof(SegmentInventory::Segment));
    current += sizeof(SegmentInventory::Segment);
    memcpy(current, &freeEntry, sizeof(SegmentInventory::Entry));

    buffMan.unfixPage(frame, true);
}

void SegmentInventory::printInventory()
{
    cout << "print" << endl;
    for (unsigned i = 0; i < this->segments.size(); i++)
    {
        cout << "#" << i << ": SegId: " << (this->segments[i])->segmentId << " entryCount: " <<  (this->segments[i])->entryCount << endl;

        for (unsigned n = 0; n < this->entries[i].size(); n++)
        {
            cout << "     from: " << (this->entries[i])[n]->from << " to: " << (this->entries[i])[n]->to << endl;
        }
    }
}


/**
* Teilt gegebene Seitennummern zu bestehenden Segmenten hinzu bzw. erstellt neue. Dabei werden auch dadurch
* ohne Lücke nebeneinanderliegende Segmente vereinigt.
*
* ACHTUNG: entries muss bereits sortiert sein! In pages darf keine Nummer vorkommen, die bereits über einen
* Entry erfasst ist.
* Die Liste pages ist nach Ausführung dieser Methode leer.
*/
void SegmentInventory::merge(vector<SegmentInventory::Entry*>& entryVec, list<unsigned>& pages)
{
    pages.sort();

    // Zeigt immer auf den aktuellen Entry im Vektor
    unsigned entryIndex = 0;

    while (!pages.empty())
    {
        unsigned page = pages.front();
        pages.pop_front();

        while (true)
        {
            // Am Ende des Vektors? -> Neues Segment einfügen
            if (1 + entryIndex > entryVec.size())
            {
                SegmentInventory::Entry* newEntry = new SegmentInventory::Entry();
                newEntry->from = page;
                newEntry->to = page + 1;
                entryVec.push_back(newEntry);
                break;
            }
            else
            {
                SegmentInventory::Entry& entry = *(entryVec[entryIndex]);

                // Vor aktueller Position neues Segment einzufügen
                if (entry.from > (page+1))
                {
                    SegmentInventory::Entry* newEntry = new SegmentInventory::Entry();
                    newEntry->from = page;
                    newEntry->to = page + 1;
                    entryVec.insert(entryVec.begin()+entryIndex, newEntry);
                    break;
                }
                // Zu aktuellem Segment vorne anfügen
                else if (entry.from == (page+1))
                {
                    entry.from = page;
                    break;
                }
                // Zu aktuellem Segment hinten anfügen
                else if (entry.to == page)
                {
                    entry.to = page + 1;

                    // Kann eine Verschmelzung mit nachfolgendem Entry erfolgen?
                    if ((entryIndex+1) < entryVec.size())
                    {
                        SegmentInventory::Entry& nextEntry = *(entryVec[entryIndex+1]);
                        if (entry.to == nextEntry.from)
                        {
                            entry.to = nextEntry.to;
                            delete entryVec[entryIndex + 1];
                            entryVec[entryIndex + 1] = NULL;
                            entryVec.erase(entryVec.begin() + entryIndex + 1);
                        }
                    }
                    break;
                }
                // Aktuelles Segment enthält nur zu kleine Werte -> Srpung zum nächsten
                else
                {
                    entryIndex ++;
                }
            }
        }
    }
}

void SegmentInventory::clear()
{
    for (unsigned i = 0; i < this->segments.size(); i++)
    {
        delete this->segments[i];
        this->segments[i] = NULL;
    }
    this->segments.clear();

    for (unsigned i = 0; i < this->entries.size(); i++)
    {
        for (unsigned n = 0; n < this->entries[i].size(); n++)
        {
            delete this->entries[i][n];
            this->entries[i][n] = NULL;
        }
        this->entries[i].clear();
    }
    this->entries.clear();
}

SegmentInventory::~SegmentInventory()
{
    SEM_DESTROY(fileSemaphore);
    this->clear();
}


list<unsigned>* SegmentInventory::removeIfContained(vector<SegmentInventory::Entry*>& entries, list<unsigned>& pages)
{
    pages.sort();

    list<unsigned>* result = new list<unsigned>();

    // Zeigt immer auf den aktuellen Entry im Vektor
    unsigned entryIndex = 0;

    while (!pages.empty())
    {

        unsigned page = pages.front();
        pages.pop_front();

        while (true)
        {

            // Am Ende des Vektors? -> Neues Segment einfügen
            if (entryIndex > (entries.size()-1))
            {
                break;
            }

            else
            {
                SegmentInventory::Entry& entry = *(entries.at(entryIndex));

                // Segment beginnt mit gesuchter Zahl
                if (entry.from == page)
                {
                    result->push_back(page);
                    entry.from ++;
                    if (entry.from==entry.to)
                    {
                        delete entries[entryIndex];
                        entries[entryIndex] = NULL;
                        entries.erase(entries.begin()+entryIndex);
                    }
                    break;
                }

                // Segment endet mit gesuchter Zahl
                else if (entry.to == (page+1))
                {
                    result->push_back(page);
                    entry.to --;
                    if (entry.from==entry.to)
                    {
                        delete entries[entryIndex];
                        entries[entryIndex] = NULL;
                        entries.erase(entries.begin()+entryIndex);
                    }
                    break;
                }

                // Zahl in Segment enthalten, Segment muss aufgeteilt werden
                else if (entry.from<page && entry.to>(page+1))
                {
                    result->push_back(page);

                    SegmentInventory::Entry* newEntry = new SegmentInventory::Entry();
                    newEntry->from = page + 1;
                    newEntry->to = entry.to;
                    entries.insert(entries.begin()+entryIndex+1, newEntry);

                    entry.to = page;

                    entryIndex ++;
                    break;
                }

                // Aktuelles Segment enthält nur zu kleine Werte -> Srpung zum nächsten
                else
                {
                    entryIndex ++;
                }
            }
        }
    }
    delete result;
    return NULL;
}
